﻿using System.Windows.Media;
using Microsoft.Devices.Sensors;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Device.Location;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using Windows.Devices.Geolocation;
using RadarControl.Abstracts;
using RadarControl.Core.GeoItems;

namespace RadarControl.Core
{
    [TemplatePart(Name = RadarControl.PartNames.RadarCanvas, Type = typeof(Canvas))]
    [TemplatePart(Name = RadarControl.PartNames.HeadingElement, Type = typeof(UIElement))]
    public class RadarControl: Control
    {
        #region Part Names
        static internal class PartNames
        {
            public const string RadarCanvas = "PART_RadarIndicator";
            public const string HeadingElement = "PART_HeadingIndicator";
        }
        #endregion

        #region Events & EventHandlers
        public event Func<PointsRequestedEventArgs, IEnumerable<IRadarPoint>> PointsReuqested = delegate
        {
            return Enumerable.Empty<IRadarPoint>();
        };

        void gpsService_PositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
        {
            Deployment.Current.Dispatcher.BeginInvoke(() =>
            {
                var items = PointsReuqested(new PointsRequestedEventArgs()
                {
                    CurrentLocation = e.Position.Location,
                    DesiredPointDistance = MaxPointDistance
                });

                if (items != null)
                {
                    radarPoints = new List<IRadarPoint>(items);

                    DrawPointsOnCanvas();
                }
               
            });
        }

        void compassService_CurrentValueChanged(object sender, SensorReadingEventArgs<CompassReading> e)
        {
            Deployment.Current.Dispatcher.BeginInvoke(() =>
                {
                    if (InvertRotation)
                    {
                        Rotation = 360 - Math.Abs(e.SensorReading.TrueHeading) * (-1);
                    }
                    else
                    {
                        Rotation = 360 - Math.Abs(e.SensorReading.TrueHeading);
                    }
                });
            
        }

        void compassService_Calibrate(object sender, CalibrationEventArgs e)
        {
            
        }
        #endregion

        #region Fields
        GeoCoordinateWatcher gpsService;
        Compass compassService;

        List<IRadarPoint> radarPoints = new List<IRadarPoint>();

        //UI Elevemtns
        private Canvas radarCanvas;
        #endregion

        #region Constructor
        public RadarControl()
        {
            DefaultStyleKey = typeof(RadarControl);

            if (DesignerProperties.GetIsInDesignMode(this))
                return;

            gpsService = new GeoCoordinateWatcher(GeoPositionAccuracy.High);
            compassService = new Compass();

            InitalizeCompassModule(true);
        }
        #endregion

        #region Dependency Properties

        [Category("RadarSettings")]
        public bool IsLocationEnabled
        {
            get { return (bool)GetValue(IsLocationEnabledProperty); }
            set 
            { 
                SetValue(IsLocationEnabledProperty, value); 
            }
        }
        public static readonly DependencyProperty IsLocationEnabledProperty =
            DependencyProperty.Register("IsLocationEnabled", typeof(bool), typeof(RadarControl), new PropertyMetadata(false, IsLocationEnabledChangedCallback));

        public static void IsLocationEnabledChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((RadarControl)d).OnLocationEnabledChanged(e);
        }

        [Category("RadarSettings")]
        public double Rotation
        {
            get { return (double)GetValue(RotationProperty); }
            set { SetValue(RotationProperty, value); }
        }
        public static readonly DependencyProperty RotationProperty =
            DependencyProperty.Register("Rotation", typeof(double), typeof(RadarControl), new PropertyMetadata((double)0));

        [Category("RadarSettings")]
        /// <summary>
        /// Describes maximus distance of point that can be viewed.
        /// </summary>
        public int MaxPointDistance
        {
            get { return (int)GetValue(MaxPointDistanceProperty); }
            set { SetValue(MaxPointDistanceProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MaxPointDistance.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MaxPointDistanceProperty =
            DependencyProperty.Register("MaxPointDistance", typeof(int), typeof(RadarControl), new PropertyMetadata(300));

        [Category("RadarSettings")]
        public bool InvertRotation
        {
            get { return (bool)GetValue(InvertRotationProperty); }
            set { SetValue(InvertRotationProperty, value); }
        }

        // Using a DependencyProperty as the backing store for InvertRotation.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty InvertRotationProperty =
            DependencyProperty.Register("InvertRotation", typeof(bool), typeof(RadarControl), new PropertyMetadata(false));

        [Category("RadarSettings")]
        public double GeoPointsRotation
        {
            get { return (double)GetValue(GeoPointsRotationProperty); }
            set { SetValue(GeoPointsRotationProperty, value); }
        }


        // Using a DependencyProperty as the backing store for GeoPointsRotation.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty GeoPointsRotationProperty =
            DependencyProperty.Register("GeoPointsRotation", typeof(double), typeof(RadarControl), new PropertyMetadata((double)180));


        [Category("RadarSettings")]
        public ControlTemplate GeoPointTemplate
        {
            get { return (ControlTemplate)GetValue(GeoPointTemplateProperty); }
            set { SetValue(GeoPointTemplateProperty, value); }
        }

        // Using a DependencyProperty as the backing store for GeoPointTemplate.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty GeoPointTemplateProperty =
            DependencyProperty.Register("GeoPointTemplate", typeof(ControlTemplate), typeof(RadarControl), new PropertyMetadata(null));


        #endregion

        #region Overrides
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            radarCanvas = GetTemplateChild(PartNames.RadarCanvas) as Canvas;

        }

        #endregion

        #region Virtual methods
        protected virtual void OnLocationEnabledChanged(DependencyPropertyChangedEventArgs e)
        {
            InitalizeLocationModule((bool)e.NewValue);
        }

        protected virtual void OnGeoItemsChanged(DependencyPropertyChangedEventArgs e)
        {
            DrawPointsOnCanvas();
        }
        #endregion

        #region Methods
        private void InitalizeLocationModule(bool initalizeModule)
        {
            if (DesignerProperties.IsInDesignTool)
                return;

            if (initalizeModule)
            {
                gpsService.MovementThreshold = 1;

                gpsService.PositionChanged -= gpsService_PositionChanged;//unregister if already registered
                gpsService.PositionChanged += gpsService_PositionChanged;

                gpsService.Start();
            }
            else
            {
                gpsService.Stop();
                gpsService.PositionChanged -= gpsService_PositionChanged;
            }
        }

        private void InitalizeCompassModule(bool initalizeModule)
        {
            if (DesignerProperties.IsInDesignTool)
                return;

            if (initalizeModule)
            {
                compassService.Calibrate -= compassService_Calibrate;
                compassService.CurrentValueChanged -= compassService_CurrentValueChanged; //unregister if already registered
                compassService.Calibrate += compassService_Calibrate;
                compassService.CurrentValueChanged += compassService_CurrentValueChanged;

                compassService.Start();
            }
            else
            {
                compassService.Stop();
                compassService.Calibrate -= compassService_Calibrate;
                compassService.CurrentValueChanged -= compassService_CurrentValueChanged;
            }
        }

        public Point ItemToPoint(IRadarPoint point)
        {
            //coords rotation
            double angle = GeoPointsRotation + (double)point.Azimuth;

            // turn into radians
            angle *= 0.0174532925d;

            double r, x, y;
            //300 is the canvas size. Rest is scaled by viewbox.
            // determine the radius length
            r = (double) 300*0.5d;
            var maxPointDistanceWithStroke = MaxPointDistance * 1.05;
            r = (r * (double)point.Distance / (double)(maxPointDistanceWithStroke));

            x = (((double) 300*0.5d) + (r*Math.Cos(angle)));
            y = (((double) 300*0.5d) + (r*Math.Sin(angle)));

            return new Point(x, y);
        }

        #endregion

        #region Drawing Methods
        private void DrawPointsOnCanvas()
        {
            
            if (radarCanvas != null && radarPoints != null)
            {
                var itemsToClear =
                    radarCanvas.Children.Where(x => ((FrameworkElement) x).Tag != null)
                        .Where(x => ((FrameworkElement) x).Tag.ToString() == "arItem");

                for (int i = itemsToClear.Count() -1; i >= 0; i--)
                {
                    radarCanvas.Children.Remove(itemsToClear.ElementAt(i));
                }

                foreach (var radarPoint in radarPoints)
                {
                    var position = ItemToPoint(radarPoint);
                    var pointUiElement = radarPoint.GetUiElement();

                    if (pointUiElement is ContentControl)
                    {
                        ((ContentControl)pointUiElement).Template = GeoPointTemplate;
                    }

                    Canvas.SetTop(pointUiElement,position.Y);
                    Canvas.SetLeft(pointUiElement,position.X);

                    radarCanvas.Children.Add(pointUiElement);
                }

                radarCanvas.UpdateLayout();
            }
        }
        #endregion
    }
}
